﻿using ED.Common.BaseModel;
using ED.Common.Extensions;
using ED.Common.Utils;
using Grpc.Core;

namespace ED.BaseServerGRPC.Interceptor
{
    /// <summary>
    /// 拦截器
    /// </summary>
    public class ServerInterceptor : global::Grpc.Core.Interceptors.Interceptor
    {
        private readonly ILogger<ServerInterceptor> logger;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="logger">日志记录类</param>
        public ServerInterceptor(ILogger<ServerInterceptor> logger)
        {
            this.logger = logger;
        }

        // BlockingUnaryCall 拦截阻塞调用
        // AsyncUnaryCall 拦截异步调用
        // AsyncServerStreamingCall 拦截异步服务端流调用
        // AsyncClientStreamingCall 拦截异步客户端流调用
        // AsyncDuplexStreamingCall 拦截异步双向流调用
        // UnaryServerHandler 用于拦截和传入普通调用服务器端处理程序
        // ClientStreamingServerHandler 用于拦截客户端流调用的服务器端处理程序
        // ServerStreamingServerHandler 用于拦截服务端流调用的服务器端处理程序
        // DuplexStreamingServerHandler 用于拦截双向流调用的服务器端处理程序

        /// <inheritdoc/>
        public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation)
            where TResponse : class
        {
            return await Monitor<TRequest, TResponse>(request, context, continuation);

            // logger.LogInformation($"Starting call. Type: {MethodType.Unary}. Request: {typeof(TRequest)},message:{JsonExtensions.ToJson(request)}. Response: {typeof(TResponse)}");
            // return continuation(request, context);
        }

        private async Task<TResponse> Monitor<TRequest, TResponse>(object request, ServerCallContext context, Delegate continuation, object response = null)
            where TResponse : class
        {
            var trace = context.RequestHeaders.FirstOrDefault(q => q.Key == "traceid");
            if (trace == null)
            {
                trace = new Metadata.Entry("traceid", Guid.NewGuid().ToString());
                context.RequestHeaders.Add(trace);
            }

            var model = new MonitorModel
            {
                ClientIp = context.Peer,
                RequestUrl = context.Method,
                RequestData = request?.ToJson(),
                RequestHeaders = context.RequestHeaders.ToDictionary(p => p.Key, p => p.Value),
                TraceId = trace.Value
            };
            if (request is TRequest)
            {
                model.RequestData = request?.ToJson();
            }
            else if (request is IAsyncStreamReader<TRequest>)
            {
                var requests = new List<TRequest>();

                // await requestStream.ForEachAsync(req=> {
                //    requests.Add(req);
                //    return Task.CompletedTask;
                // });
                model.RequestData = requests?.ToJson();
            }

            try
            {
                if (response == null)
                {
                    var res = continuation.DynamicInvoke(request, context);
                    if (res == null)
                    {
                        return null;
                    }

                    var task = res as Task<TResponse>;
                    var result = await task;
                    model.Status = "ok";
                    model.ResponseData = result?.ToJson();

                    return result;
                }
                else
                {
                    await (continuation.DynamicInvoke(request, response, context) as Task);
                    return default(TResponse);
                }
            }
            catch (Exception ex)
            {
                var rpcEx = new RpcException(new Status(StatusCode.Internal, ex.Message));
                var dataRequest = rpcEx.Data["Request"];
                if (dataRequest != null)
                {
                    model.Items.TryAdd("ClientRequest", dataRequest);
                    rpcEx.Data["Request"] = model;
                }
                else
                {
                    rpcEx.Data.Add("Request", model);
                }

                model.Exception = rpcEx.ToString();
                model.Status = "error";
                logger.LogError(model.ToJson());
                if (typeof(TResponse).FullName == "ED.Common.BaseModel.ResponseOutput" || typeof(TResponse).FullName.StartsWith("ED.Common.BaseModel.ResponseOutput`1"))
                {
                    TResponse res = ResponseOutput.NotOk($"{ex.Message}", 500).MapTo<TResponse>();
                    return res;
                }
                else
                {
                    throw rpcEx;
                }
            }
            finally
            {
                model.ResponseTime = DateTime.Now;
                logger.LogInformation("{content}", model);
            }
        }
    }
}
